package gsw.tool.dex.core;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.RandomAccessFile;

import gsw.tool.dex.util.Tool;

/**
 * 加密dex，并签名apk
 * 使用步骤见README.md
 */
public class Sign {
    static KeyStore keyStore = null;//签名文件配置
    static String dexPath = "ToolDex";//加密库所在根路径
    static String appPath = "ToolDexDemo";//需要加密的Module所在根路径
    static String ksPath = appPath + "/test.keystore";//签名文件路径
    static String ksAlias = "test";//签名文件别名
    static String ksPass = "qq123456";//签名文件密码
    static String ksPassKey = "qq123456";//签名密码
    static boolean isWindow = false;//是否window操作系统

    public static void main(String[] args) throws Exception {
        initKeyStore();
        encryptAndSign();
    }

    /**
     * 加密dex，并签名apk
     */
    private static void encryptAndSign() throws Exception {
        //1、制作加密库的dex 文件
        File classesDex = unZipAar();
        //2、加密apk中所有dex文件
        File apkTemp = encryptDex();
        //3、把classes.dex放入apk解压目录，在压缩成未签名的apk
        File unSignedApk = zipDex(classesDex, apkTemp);
        //4、对齐与签名
        siginApk(unSignedApk);
    }

    /**
     * 1、制作加密库的dex 文件
     * 先将加密库编译成aar文件，然后对其解压得到classes.jar,再通过dx命令将其打包成dex文件。
     */
    private static File unZipAar() throws IOException, InterruptedException {
        File aarFile = new File(dexPath + "/build/outputs/aar/" + dexPath + "-debug.aar");
        File aarTemp = new File(dexPath + "/temp");
        //解压aar 获得classes.jar
        Zip.unZip(aarFile, aarTemp);
        File classesJar = new File(aarTemp, "classes.jar");
        //执行dx命令 将jar变成dex文件
        File classesDex = new File(aarTemp, "classes.dex");

        //执行cmd命令。需要把dx工具添加环境变量，并重启AS。
        String cmd = "dx --dex --output " + classesDex.getAbsolutePath() + " " + classesJar.getAbsolutePath();
        exec(cmd);
        return classesDex;
    }

    /**
     * 2、加密apk中所有dex文件
     * 解压apk，得到所有的dex文件，然后使用AES加密。
     *
     * @return
     */
    private static File encryptDex() throws Exception {
        //2.1 解压apk 获得所有的dex文件
        File apkFile = new File(appPath + "/build/outputs/apk/debug/" + appPath + "-debug.apk");
        File apkTemp = new File(appPath + "/build/outputs/apk/debug/temp");
        Zip.unZip(apkFile, apkTemp);
        //获得所有的dex
        File[] dexFiles = apkTemp.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File file, String s) {
                return s.endsWith(".dex");
            }
        });
        //初始化aes
        AES.init(AES.DEFAULT_PWD);
        for (File dex : dexFiles) {
            //读取文件数据
            byte[] bytes = getBytes(dex);
            //加密
            byte[] encrypt = AES.encrypt(bytes);
            //写到指定目录
            FileOutputStream fos = new FileOutputStream(new File(apkTemp, "secret-" + dex.getName()));
            fos.write(encrypt);
            fos.flush();
            fos.close();
            dex.delete();
        }
        return apkTemp;
    }

    /**
     * 3、把classes.dex 放入 apk解压目录 在压缩成apk
     * 将加密库的dex文件和apk的dex文件打包成新的apk，即为加密后的apk。
     */
    private static File zipDex(File classesDex, File apkTemp) throws Exception {
        classesDex.renameTo(new File(apkTemp, "classes.dex"));
        File unSignedApk = new File(appPath + "/build/outputs/apk/debug/\" + appPath + \"-unsigned.apk");
        Zip.zip(apkTemp, unSignedApk);
        return unSignedApk;
    }

    /**
     * 4、对齐并签名apk
     * 先对加密后的apk对齐，压缩其体积，然后再进行签名。
     *
     * @param unSignedApk 未签名的apk
     */
    private static void siginApk(File unSignedApk) throws IOException, InterruptedException {
        //对齐apk文件：压缩apk体积。26.0.2不认识-p参数 zipalign -v -p 4 my-DemoDex-unsigned.apk my-DemoDex-unsigned-aligned.apk
        File alignedApk = new File(appPath + "/build/outputs/apk/debug/\" + appPath + \"-unsigned-aligned.apk");
        String cmd = "zipalign -f 4 " + unSignedApk.getAbsolutePath() + " " + alignedApk.getAbsolutePath();
        exec(cmd);

        //如果配置了签名文件，就对apk签名
        if (keyStore != null) {
            //签名：apksigner sign  --ks jks文件地址 --ks-key-alias 别名 --ks-pass pass:jsk密码 --key-pass pass:别名密码 --out  out.apk in.apk
            File signedApk = new File(appPath + "/build/outputs/apk/debug/\" + appPath + \"-signed-aligned.apk");
            //注意：apksigner工具在build-tools26.0.0中没有，可以用26.0.3的
            String cmd2 = "apksigner sign  --ks " + keyStore.path + " --ks-key-alias " + keyStore.alias + " --ks-pass pass:" + keyStore.ks_pass + " --key-pass " +
                    "pass:" + keyStore.key_pass + " --out" + " " + signedApk.getAbsolutePath() + " " + alignedApk.getAbsolutePath();
            exec(cmd2);
        }

        //删除临时文件
        File aarTemp = new File(dexPath + "/temp");
        Tool.deleteDir(aarTemp);
        File apkTemp = new File(appPath + "/build/outputs/apk/debug/temp");
        Tool.deleteDir(apkTemp);
        //删除未签名的文件
        Tool.deleteDir(unSignedApk);
        //如果apk签名了，就删除对齐的文件
        if (keyStore != null) {
            Tool.deleteDir(alignedApk);
        }
    }

    /**
     * 执行cmd命令
     *
     * @param cmd cmd命令语句
     * @throws IOException
     * @throws InterruptedException
     */
    private static void exec(String cmd) throws IOException, InterruptedException {
        //windows中需要以cmd /c开头，linux/mac不需要
        if (isWindow) {
            cmd = "cmd /c " + cmd;
        }
        Process process = Runtime.getRuntime().exec(cmd);
        process.waitFor();
        //失败
        if (process.exitValue() != 0) {
            throw new RuntimeException("apksigner error");
        }
    }

    /**
     * 获取文件的byte数组
     */
    public static byte[] getBytes(File file) throws Exception {
        RandomAccessFile r = new RandomAccessFile(file, "r");
        byte[] buffer = new byte[(int) r.length()];
        r.readFully(buffer);
        r.close();
        return buffer;
    }

    /**
     * 初始化签名文件
     */
    public static void initKeyStore() {
        if (Tool.isEmpty(ksPath)) {
            return;
        }
        keyStore = new KeyStore(ksPath, ksAlias, ksPass, ksPassKey);
    }

    /**
     * 签名文件配置
     */
    public static class KeyStore {
        //签名文件路径
        String path;
        //签名文件别名
        String alias;
        //签名文件密码
        String ks_pass;
        //签名密码
        String key_pass;

        public KeyStore(String path, String alias, String ks_pass, String key_pass) {
            this.path = path;
            this.alias = alias;
            this.ks_pass = ks_pass;
            this.key_pass = key_pass;
        }
    }
}
